# ...existing code...
from flask import Flask, request, jsonify
import os
from datetime import datetime, date, timedelta
from typing import Optional, Dict, Any

from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

# DB config (use provided credentials by default)
db_url = os.getenv('BORROWS_DB_URL')
if not db_url:
    DB_USER = os.getenv('DB_USER', 'root')
    DB_PASS = os.getenv('DB_PASS', '8209230329')
    DB_HOST = os.getenv('DB_HOST', 'localhost')
    DB_NAME = os.getenv('DB_NAME', 'soa_lab1')
    db_url = f"mysql+pymysql://{DB_USER}:{DB_PASS}@{DB_HOST}/{DB_NAME}?charset=utf8mb4"

app.config['SQLALCHEMY_DATABASE_URI'] = db_url
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

# ---------- helpers ----------


def parse_date_from_str(s: Optional[str]) -> Optional[date]:
    if not s:
        return None
    s = s.strip()
    try:
        if 'T' in s:
            return datetime.fromisoformat(s.replace('Z', '+00:00')).date()
        return datetime.strptime(s, '%Y-%m-%d').date()
    except Exception:
        try:
            return datetime.fromisoformat(s).date()
        except Exception:
            return None


def date_to_str_iso(d: Optional[date]) -> Optional[str]:
    if not d:
        return None
    return d.isoformat()  # keep simple YYYY-MM-DD to match stored format


def now_str() -> str:
    return datetime.utcnow().isoformat() + 'Z'

# ---------- ORM model mapped to your exact table schema ----------


class BorrowRecord(db.Model):
    __tablename__ = 'BorrowRecord'   # exactly as your SQL
    id = db.Column('id', db.BigInteger, primary_key=True)
    userid = db.Column('userid', db.BigInteger, nullable=True)
    username = db.Column('username', db.String(255), nullable=True)
    materialId = db.Column('materialId', db.BigInteger, nullable=True)
    materialName = db.Column('materialName', db.String(255), nullable=True)
    borrowDate = db.Column('borrowDate', db.String(50), nullable=True)
    expectedReturn = db.Column('expectedReturn', db.String(50), nullable=True)
    actualReturn = db.Column('actualReturn', db.String(50), nullable=True)
    status = db.Column('status', db.String(50), nullable=True)
    purpose = db.Column('purpose', db.String(255), nullable=True)
    createTime = db.Column('createTime', db.String(50), nullable=True)
    updateTime = db.Column('updateTime', db.String(50), nullable=True)

    # createRecord(): 创建借用记录
    @classmethod
    def createRecord(cls, payload: Dict[str, Any]) -> 'BorrowRecord':
        # determine new id if table has no AUTO_INCREMENT
        max_id = db.session.query(db.func.max(cls.id)).scalar() or 0
        new_id = int(max_id) + 1
        now = now_str()
        # prepare fields, accept legacy keys 'item'/'borrower'
        rec = cls(
            id=new_id,
            userid=int(payload.get('userid') or payload.get('userId') or 0),
            username=payload.get('username') or payload.get(
                'userName') or payload.get('borrower') or '',
            materialId=int(payload.get('materialId')
                           or payload.get('materialID') or 0),
            materialName=payload.get('materialName') or payload.get(
                'material') or payload.get('item') or '',
            borrowDate=payload.get('borrowDate') or payload.get(
                'date') or date_to_str_iso(datetime.utcnow().date()),
            expectedReturn=payload.get('expectedReturn') or date_to_str_iso(
                (datetime.utcnow().date() + timedelta(days=7))),
            actualReturn=payload.get('actualReturn'),
            status=payload.get('status') or 'borrowed',
            purpose=payload.get('purpose') or '',
            createTime=payload.get('createTime') or now,
            updateTime=payload.get('updateTime') or now
        )
        return rec

    # returnMaterial(): 归还物资操作
    def returnMaterial(self, actual_date: Optional[str] = None) -> None:
        if self.status == 'returned':
            return
        if actual_date:
            self.actualReturn = actual_date
        else:
            self.actualReturn = date_to_str_iso(datetime.utcnow().date())
        self.status = 'returned'
        self.updateTime = now_str()

    # extendBorrowTime(): 延长借用时间
    def extendBorrowTime(self, newExpectedReturn: Optional[str] = None, extendDays: Optional[int] = None) -> bool:
        try:
            if newExpectedReturn:
                # validate
                if not parse_date_from_str(newExpectedReturn):
                    return False
                self.expectedReturn = newExpectedReturn
            elif extendDays is not None:
                cur = parse_date_from_str(
                    self.expectedReturn) or datetime.utcnow().date()
                self.expectedReturn = date_to_str_iso(
                    cur + timedelta(days=int(extendDays)))
            else:
                return False
            self.updateTime = now_str()
            return True
        except Exception:
            return False

    # isOverdue(): 检查是否逾期
    def isOverdue(self) -> bool:
        if self.status == 'returned':
            return False
        exp = parse_date_from_str(self.expectedReturn)
        if not exp:
            return False
        return datetime.utcnow().date() > exp

    # calculateOverdueDays(): 计算逾期天数
    def calculateOverdueDays(self) -> int:
        if not self.isOverdue():
            return 0
        exp = parse_date_from_str(self.expectedReturn)
        return max(0, (datetime.utcnow().date() - exp).days)

    # getBorrowDuration(): 获取借用时长
    def getBorrowDuration(self) -> int:
        start = parse_date_from_str(
            self.borrowDate) or datetime.utcnow().date()
        end = parse_date_from_str(
            self.actualReturn) or datetime.utcnow().date()
        return max(0, (end - start).days)

    def to_dict(self) -> Dict[str, Any]:
        return {
            'id': int(self.id) if self.id is not None else None,
            'userid': int(self.userid) if self.userid is not None else None,
            'username': self.username,
            'materialId': int(self.materialId) if self.materialId is not None else None,
            'materialName': self.materialName,
            'borrowDate': self.borrowDate,
            'expectedReturn': self.expectedReturn,
            'actualReturn': self.actualReturn,
            'status': self.status,
            'purpose': self.purpose,
            'createTime': self.createTime,
            'updateTime': self.updateTime,
            'overdue': self.isOverdue(),
            'overdueDays': self.calculateOverdueDays(),
            'borrowDuration': self.getBorrowDuration()
        }


# ensure table exists (won't modify your columns)
with app.app_context():
    db.create_all()

# ---------- REST endpoints ----------


@app.route('/')
def index():
    return jsonify({'message': 'borrow backend'})


@app.route('/borrows', methods=['GET'])
def list_borrows():
    q = BorrowRecord.query
    userid = request.args.get('userid') or request.args.get('userId')
    materialId = request.args.get('materialId')
    status = request.args.get('status')
    if userid:
        try:
            q = q.filter_by(userid=int(userid))
        except:
            pass
    if materialId:
        try:
            q = q.filter_by(materialId=int(materialId))
        except:
            pass
    if status:
        q = q.filter_by(status=status)
    items = q.order_by(BorrowRecord.id.asc()).all()
    return jsonify([i.to_dict() for i in items])


@app.route('/borrows/<int:borrow_id>', methods=['GET'])
def get_borrow(borrow_id: int):
    b = BorrowRecord.query.get(borrow_id)
    if not b:
        return jsonify({'error': 'not_found'}), 404
    return jsonify(b.to_dict())


@app.route('/borrows', methods=['POST'])
def create_borrow():
    payload = request.get_json(force=True) or {}
    # require userid and materialId (accept legacy keys)
    if not (payload.get('userid') or payload.get('userId')) or not (payload.get('materialId')):
        if not (payload.get('item') and payload.get('borrower')):
            return jsonify({'error': 'invalid_request', 'message': 'userid and materialId required'}), 400
    rec = BorrowRecord.createRecord(payload)
    db.session.add(rec)
    db.session.commit()
    return jsonify(rec.to_dict()), 201


@app.route('/borrows/<int:borrow_id>', methods=['PUT'])
def update_borrow(borrow_id: int):
    b = BorrowRecord.query.get(borrow_id)
    if not b:
        return jsonify({'error': 'not_found'}), 404
    payload = request.get_json(force=True) or {}

    # return operation
    if payload.get('action') == 'return' or payload.get('actualReturn') or payload.get('status') == 'returned':
        actual = payload.get('actualReturn')
        b.returnMaterial(actual_date=actual)
        db.session.commit()
        return jsonify(b.to_dict())

    # extend
    if payload.get('newExpectedReturn') or ('extendDays' in payload):
        ok = b.extendBorrowTime(newExpectedReturn=payload.get(
            'newExpectedReturn'), extendDays=payload.get('extendDays'))
        if not ok:
            return jsonify({'error': 'invalid_request', 'message': 'invalid extend params'}), 400
        db.session.commit()
        return jsonify(b.to_dict())

    # normal updates
    if 'expectedReturn' in payload:
        b.expectedReturn = payload.get('expectedReturn')
    if 'purpose' in payload:
        b.purpose = payload.get('purpose')
    if 'status' in payload:
        b.status = payload.get('status')
    if 'actualReturn' in payload:
        b.actualReturn = payload.get('actualReturn')
        b.status = 'returned'
    if 'username' in payload or 'userName' in payload:
        b.username = payload.get('username') or payload.get('userName')
    if 'materialName' in payload:
        b.materialName = payload.get('materialName')

    b.updateTime = now_str()
    db.session.commit()
    return jsonify(b.to_dict())


@app.route('/borrows/<int:borrow_id>/return', methods=['POST'])
def return_borrow(borrow_id: int):
    b = BorrowRecord.query.get(borrow_id)
    if not b:
        return jsonify({'error': 'not_found'}), 404
    payload = request.get_json(force=True) or {}
    actual = payload.get('actualReturn')
    b.returnMaterial(actual_date=actual)
    db.session.commit()
    return jsonify(b.to_dict())


@app.route('/borrows/<int:borrow_id>', methods=['DELETE'])
def delete_borrow(borrow_id: int):
    b = BorrowRecord.query.get(borrow_id)
    if not b:
        return jsonify({'error': 'not_found'}), 404
    db.session.delete(b)
    db.session.commit()
    return '', 204


if __name__ == '__main__':
    host = os.getenv('APP_HOST', '0.0.0.0')
    port = int(os.getenv('APP_PORT', os.getenv('PORT', '8081')))
    app.run(debug=True, host=host, port=port)
# ...existing code...
